package emulator.shell.GUI.debugger;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import emulator.EmulatorException;
import emulator.hardware.HwWord;
import emulator.hardware.nmos6502.Flags6502;
import emulator.hardware.nmos6502.stack.CpuStack;
import emulator.hardware.nmos6502.stack.StackData;
import emulator.hardware.nmos6502.stack.StackEntry;

@SuppressWarnings("serial")
public class StackPanel extends ScrollView {
	private CpuStack stack = null;
	private boolean is_running;
	private int visible_lines = 1;
	private int current_position = 0;
	private int current_range = 1;
	private StackEntry[] stack_contents = null;
	private int selected_line = -1;
	CodePanel code_panel = null;

	public StackPanel(CpuStack stack, CodePanel code_panel)
	{
		super();
		this.stack = stack;
		this.code_panel = code_panel;
		is_running = true;
		initPanel();
	}

	private void initPanel() {
		setLayout(new BorderLayout());

		initScrollBar();

        scroll_pane = new StackPane();
        add(scroll_pane, BorderLayout.CENTER);
        scroll_pane.addKeyListener(new KeyboardListener());
		addMouseWheelListener(new MouseScrollListener());
	
		addKeyListener(new KeyboardListener());
	}

	public void updateRunState(boolean run_state) {
		is_running = run_state;
		if (!is_running)
		{
			try {
				stack_contents = stack.getContents();
			} catch (EmulatorException e) {
				stack_contents = new StackEntry [0];
			}
			setCurrentRange(stack_contents.length);
			setCurrentPosition(0);
			selectLine(-1);
		}
		showScrollBar(isPanelActive());
		repaint();
	}

	@Override
	boolean isPanelActive() {
		return !is_running;
	}

	@Override
	void setCurrentPosition(int desired_position) {
		current_position = Math.max(0, Math.min(desired_position, getCurrentRange()-getVisibleLines()));
	}

	@Override
	int getCurrentPosition() {
		return current_position;
	}

	@Override
	void setCurrentRange(int current_range) {
		this.current_range = current_range;
	}

	@Override
	int getCurrentRange() {
		return current_range;
	}

	@Override
	void setVisibleLines(int i) {
		visible_lines = i;
	}

	@Override
	int getVisibleLines() {
		return visible_lines;
	}


	public void selectLine(int line) {
		selected_line  = (line < getCurrentRange() ? line : -1);
	}


	public void jumpToCode(int hitTest) {
		if (selected_line >= 0 && selected_line < stack_contents.length)
		{
			StackEntry entry = stack_contents[selected_line];
			int source = getSourceLocation(entry);
            if (source >= 0)
            	showCode(source);
		}
	}

	private int getSourceLocation(StackEntry entry) {
		int source = -1;
		switch (entry.getSource())
		{
		case StackData.SOURCE_BREAK:
		case StackData.SOURCE_JUMP:
			source = (int)entry.getData().getNumber() - 2;
			break;
		case StackData.SOURCE_INTERRUPT:
			source = (int)entry.getData().getNumber();
			break;
		}
		return source;
	}

	private void showCode(int source) {
		if (code_panel != null)
			code_panel.showCode(source, true);
	}

	public class StackPane extends ScrollPane
	{
	    public StackPane()
		{
			addMouseListener(new MouseEventHandler());
		}

		@Override
		public void paint(Graphics g) {
	        Dimension d = getMinimumSize();
	        g.setColor(Color.white);
	        g.fillRect(0,0,d.width,d.height);
	
	        g.setFont(getTextFont());
	        
	        if (isPanelActive())
	        {
				for (int i = 0; i < getVisibleLines(); i++)
				{
					int index = getCurrentPosition()+i;

					g.setColor(Color.white);
					g.fillRect(getBorderSize(), getBorderSize()+i*getLineMetrics().getLineHeight()+1, getLineMetrics().getLineWidth(), getLineMetrics().getLineHeight()-1);
					g.setColor(Color.black);
					
					if (index == selected_line)
					{
						g.setColor(Color.blue);
						g.fillRect(getBorderSize(), getBorderSize()+i*getLineMetrics().getLineHeight()+1, getLineMetrics().getLineWidth()-1, getLineMetrics().getLineHeight()-1);
						g.setColor(Color.white);
					}

					if (index >= 0 && index < stack_contents.length)
					{
						StackEntry entry = stack_contents[index];
						int x = 0;
						
			            String s=("00"+Integer.toHexString(entry.getSp()).toUpperCase());
			            s = "[$" + s.substring(s.length()-2) + "]";
			            printString(g, s, x, i);
			            x += 6;
						
			            printString(g, entry.getData().toString(), x, i);
			            x += 7;
			            
			            switch (entry.getSource())
			            {
			            case StackData.SOURCE_BREAK:
			            	printString(g, "[BRK]", x, i);
			            	break;
			            case StackData.SOURCE_INTERRUPT:
			            	printString(g, "[INT]", x, i);
			            	break;
			            case StackData.SOURCE_JUMP:
			            	printString(g, "[JSR]", x, i);
			            	break;
			            default:
			            	if (entry.getData() instanceof Flags6502)
			            		printString(g, "\""+((Flags6502)(entry.getData())).toFlagString()+"\"", x ,i);
			            	break;
			            }
			            x += 12;
			            
			            int source = getSourceLocation(entry);
			            if (source >= 0)
			            	printString(g, "*=$"+new HwWord(source).toString(), x, i);
			            x += 10;
			            
			            if (source >= 0)
			            	printString(g, code_panel.getLabel(source), x, i);
					}
				}
	        }
	        else
	        {
		        g.setColor(Color.darkGray);
        		printString(g, "[Stack unavailable while CPU is running]", 0, 0);
	        }

		}
		
		class MouseEventHandler extends MouseAdapter
		{
			private void handleSelect(Point point) {
				requestFocusInWindow();
				selectLine(hitTest(point));
				repaint();
			}

			private void handleDoubleClick(Point point) {
				jumpToCode(hitTest(point));
				repaint();
			}

			/* (non-Javadoc)
			 * @see java.awt.event.MouseAdapter#mouseClicked(java.awt.event.MouseEvent)
			 */
			@Override
			public void mouseClicked(MouseEvent event) {
				handleSelect(event.getPoint());
				if (event.getClickCount() == 2)
					handleDoubleClick(event.getPoint());
			}
			
		}
	}

}
